<?php
declare(strict_types=1);

namespace Zms\Service;

use App\AdminApi\Module\System\Enums\ResourcesCateState;
use App\Sys;
use Hyperf\Filesystem\FilesystemFactory;
use Hyperf\HttpMessage\Upload\UploadedFile;
use JetBrains\PhpStorm\ArrayShape;
use League\Flysystem\Config;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Zms\Exception\AppException;
use Zms\Mime\Mime;
use Zms\Unit\Encryption;
use function Hyperf\Config\config;
use function Hyperf\Support\env;

/**
 * 文件上传服务
 */
class Uploader
{
    /**
     * @throws AppException
     */
    public function getConfigure(string $type, ?string $drive = null): array
    {
        if (!ResourcesCateState::Exists($type, ResourcesCateState::TAG['TYPE'])) {
            throw new AppException('类型错误,支持以下类型上传:' . implode(',', ResourcesCateState::Values(ResourcesCateState::TAG['TYPE'])));
        }
        $drive = $drive ?? config('file.default');
        if ($drive === 'local') {
            return [
                'driver' => 'local',
                'type' => $type,
                'host' => $this->domain(),
                ...$this->getUploadMime($type)
            ];
        } elseif ($drive === 'oss') {
            return [
                'driver' => 'oss',
                'type' => $type,
                'host' => $this->domain(),
                'conf' => $this->generateOssToken($type),
                ...$this->getUploadMime($type)

            ];
        } elseif ($drive === 'cos') {
            return [
                'driver' => 'cos',
                'type' => $type,
                'host' => $this->domain(),
                'conf' => $this->generateCosToken($type),
                ...$this->getUploadMime($type)
            ];
        }
        throw new AppException('上传方式错误');
    }


    /**
     *  Cos参数获取
     * @param $type
     * @return string
     */
    private function generateCosToken($type): string
    {
        $cos = config('file.storage.cos');
        return Encryption::encrypt([
            'region' => $cos['region'],
            'bucket' => $cos['bucket'] . '-' . $cos['app_id'],
            'secretId' => $cos['secret_id'],
            'secretKey' => $cos['secret_key'],
            'dir' => $this->createdStoragePath($type)
        ], env('ENCRYPTION_KEY'));
    }

    /**
     * 生成上传路径
     * @param string $type
     * @return string
     */
    public function createdStoragePath(string $type): string
    {
        return "/uploads/$type/" . date("Y/m-d/");
    }


    /**
     * OSS直传签名
     * @param $type
     * @return array
     * @author 竹林风@875384189 2021/9/13 11:32
     */
    #[ArrayShape(['accessid' => "mixed", 'host' => "string", 'policy' => "string", 'signature' => "string", 'expire' => "float|int", 'dir' => "string"])]
    private function generateOssToken($type): array
    {
        $id = env('OSS_ACCESS_ID');
        $key = env('OSS_ACCESS_SECRET');
        $host = 'https://' . env('OSS_BUCKET') . '.' . env('OSS_ENDPOINT');

        //获取创建目录
        $dir = $this->createdStoragePath($type);

        if (str_starts_with($dir, '/')) {
            $startsWith = substr($dir, 1); // 如果第一个字符为'/'，则去除该字符并赋值给结果变量
        } else {
            $startsWith = $dir; // 否则直接将原始字符串赋值给结果变量
        }

        $now = time();
        $expire = 600;  //签名有效期,单位S
        $end = $now + $expire;

        $expiration = str_replace('+00:00', '.000Z', gmdate('c', $end));

        //策略配置
        $conditions = [
            ["bucket" => env('OSS_BUCKET')],
            ['content-length-range', 0, 1048576000 * 4],
            ['starts-with', '$key', $startsWith],
            ["eq", '$success_action_status', "200"]
        ];


        $arr = ['expiration' => $expiration, 'conditions' => $conditions];

        $policy = json_encode($arr);

        $base64_policy = base64_encode($policy);

        $string_to_sign = $base64_policy;

        return [
            'accessid' => $id,
            'host' => $host,
            'policy' => $base64_policy,
            'signature' => base64_encode(hash_hmac('sha1', $string_to_sign, $key, true)),
            'expire' => $end,
            'dir' => $dir
        ];
    }


    /**
     * 获取上传驱动
     * @param string|null $drive
     * @return Filesystem
     */
    public function drive(?string $drive = null): Filesystem
    {
        return Sys::container(FilesystemFactory::class)->get($drive ?? config('file.default'));
    }

    /**
     * 获取资源域名
     * @return string
     */
    public function domain(): string
    {
        return Sys::conf('system.domain');
    }


    /**
     * 获取本地文件路径
     * @param $path
     * @return string
     */
    public function getLocalFilePath($path): string
    {
        return config('file.storage.local.root') . '/' . $path;
    }


    /**
     * 储存文件
     * @param UploadedFile $file
     * @param string $type
     * @param string|null $drive
     * @return array|null
     */
    public function storingFile(UploadedFile $file, string $type, ?string $drive = null): ?array
    {
        try {
            $paht = $this->createdStoragePath($type) . $this->generateFileName($file->getExtension());
            $filesystem = $this->drive($drive);
            $stream = fopen($file->getRealPath(), 'r+');
            $filesystem->writeStream($paht, $stream, [Config::OPTION_VISIBILITY => 'public', Config::OPTION_DIRECTORY_VISIBILITY => 'public']);
            if ($filesystem->has($paht)) {
                return ['path' => $paht, 'url' => $this->domain() . '/' . trim($paht, '/')];
            }
            return null;
        } catch (FilesystemException) {
            return null;
        }
    }


    /**
     * 创建文件名称
     * @param $extension
     * @return string
     */
    public function generateFileName($extension): string
    {
        return mb_substr(md5(time() . uniqid()), 8, 16) . '.' . strtolower($extension);
    }


    /**
     * 获取默认文件MIME
     * @param string $type
     * @return array
     * @throws AppException
     */
    public function getUploadMime(string $type): array
    {
        $extention = Sys::conf('uploader')[$type] ?? [];
        if (is_array($extention) && count($extention) > 0) {
            return [
                'mimes' => implode(',', Mime::getMimes($extention)),
                'extention' => $extention
            ];
        }
        switch ($type) {
            case ResourcesCateState::TYPE_IMAGE:
                return ['mimes' => 'image/*', 'extention' => []];
            case ResourcesCateState::TYPE_VIDEO:
                return ['mimes' => 'video/*', 'extention' => []];
            case ResourcesCateState::TYPE_FILE:
                return ['mimes' => '*', 'extention' => []];
        }
        throw new AppException('错误的文件上次类型');
    }


    /**
     * 检测上传文件类型和格式
     * @throws AppException
     */
    public function checkUploadFileType(UploadedFile $file, string $type): void
    {
        $extension = $file->getExtension();//获取上传文件的格式
        $filemime = $file->getMimeType();//获取可上传文件mime信息
        $upinfo = $this->getUploadMime($type);//获取可上传文件配置信息
        $extension_mime = Mime::getMime($extension);//获取文件对应的mime类型
        if (count($upinfo['extention']) === 0) {//没有限定文件类型
            //特殊类型,视频，图片，音频特殊处理
            if (in_array($type, [ResourcesCateState::TYPE_IMAGE, ResourcesCateState::TYPE_VIDEO, ResourcesCateState::TYPE_AUDIO])) {
                if (!$extension_mime) {
                    throw new AppException('无法获取' . ResourcesCateState::Explain($type) . '信息');
                }
                if (str_starts_with($filemime, "{$type}/") === false) {//大范围内对比文件类型
                    throw new AppException(ResourcesCateState::Explain($type) . '文件异常');
                }
            }
        } else {//指定了文件类型
            if (!in_array($extension, $upinfo['extention'])) {//验证上传文件格式
                throw new AppException('只支持上传' . implode(',', $upinfo['extention']) . '格式的' . ResourcesCateState::Explain($type));
            }
        }
    }
}